/**
 * UninaDocentiSync - A Java application to synchronize Unina resources
 * Copyright (C) 2011  Fabio Scippacercola (nonplay.programmer@gmail.com)
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330,
 * Boston, MA 02111-1307, USA.
 */

package uninadocenti.framework;

import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.logging.Level;
import javax.net.ssl.HttpsURLConnection;
import javax.swing.JOptionPane;
import org.jsoup.Connection;
import org.jsoup.Connection.Response;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.jsoup.select.Elements;
import uninadocentisync.ApplicationUtils;
import uninadocentisync.guibeans.LoginDialog;

/**
 *
 * @author Fabio Scippacercola (nonplay.programmer@gmail.com)
 */
public class WebDocentiUnina implements java.io.Serializable
{

    public WebDocentiUnina() {
        cookies = new HashMap<String, String>();
    }

    protected Connection addCookies(Connection c)
    {
        for (Entry<String, String> e : cookies.entrySet())
            c.cookie(e.getKey(), e.getValue());
        return c;
    }

    protected void extractCookies(Response res)
    {
        cookies.putAll(res.cookies());
    }


    public List<Docente> lookupDocente(String cognome) throws IOException
    {
        final List<Docente> d = new ArrayList<Docente>();

        if (ApplicationUtils.getInstance().isDebuggingEnabled())
            ApplicationUtils.getInstance().log(Level.FINE, String.format("lookupDocente(%s)", cognome), null);

        String url = "cognome=" + URLEncoder.encode(cognome, "UTF-8") + "&submit1=avvia+ricerca";
        Document doc = Jsoup.connect("https://www.docenti.unina.it/cercaDocente.do?" + url).get();
        Element listaDocenti = doc.getElementById("docente");

        // Se esce lista con + 2 docente
        if (listaDocenti != null)
        {
            Elements righe = listaDocenti.getElementsByTag("tr");

            for (Element riga : righe.subList(1, righe.size()))
            {
                Elements colonne = riga.getElementsByTag("td");

                String docCognome = colonne.get(0).text();
                String docNome = colonne.get(1).text();
                String dipartimento = colonne.get(2).text();
                String idDocente = parseHrefDocente(colonne.get(3).getElementsByTag("a").first().attr("href"), ParseField.idDocente);

                if (ApplicationUtils.getInstance().isDebuggingEnabled())
                    ApplicationUtils.getInstance().log(Level.FINE, String.format("<%s> <%s> <%s> <%s>", docNome, docCognome, dipartimento, idDocente), null);

                d.add(new Docente(idDocente, docNome, docCognome, dipartimento));
            }
        }
        else
        {
            for (Element refHypertext : doc.getElementsByTag("a"))
            {
                if (refHypertext.text().equalsIgnoreCase("RIFERIMENTI"))
                {
                    if (ApplicationUtils.getInstance().isDebuggingEnabled())
                        ApplicationUtils.getInstance().log(Level.FINE, refHypertext.attr("href"), null);

                    final String hrefStr = refHypertext.attr("href");
                    d.add(new Docente(parseHrefDocente(hrefStr, ParseField.idDocente),
                            parseHrefDocente(hrefStr, ParseField.nomeDocente),
                            parseHrefDocente(hrefStr, ParseField.cognomeDocente), ""));
                    break;
                }
            }
        }
        return d;
    }

    public List<Insegnamento> lookupInsegnamenti(final Docente d)
            throws IOException
    {
        final List<Insegnamento> v = new ArrayList<Insegnamento>();

        getTicket(d);
        Document doc = addCookies(Jsoup.connect("https://www.docenti.unina.it/studente/visualizzaAreeDownload.do")
          .data("idDocente", d.getIdDocente(), "nomeDocente", d.getNomeDocente(),
          "cognomeDocente", d.getCognomeDocente(), "percorso", "MATERIALE DIDATTICO")
          .userAgent("Mozilla").timeout(WebDocentiUnina.connectionTimeout)).get();

        if (ApplicationUtils.getInstance().isDebuggingEnabled())
            ApplicationUtils.getInstance().log(Level.FINE, doc.toString(), null);

        Elements cartella = doc.getElementsByAttributeValueContaining("id", "cartella");
        for (int c = 0; c < cartella.size(); c++)
        {
            Elements colonne = cartella.get(c).getElementsByTag("td");
//            System.err.println(colonne); System.err.println();
            for (int i = 0; i < colonne.size(); i += 2)
            {
//                System.err.println(colonne.get(i).text());
                final Element colonnaTitolo = colonne.get(i);
                final Element colonnaLink = colonne.get(i+1);
                v.add(new Insegnamento(colonnaTitolo.text(), d,
                        colonnaLink.getElementsByAttributeValue("name", "percorsoPath").first().attr("value"),
                        colonnaLink.getElementsByAttributeValue("name", "codInse").first().attr("value")));
            }
        }

        return v;
    }

    public InputStream openResourceStream(WebDocentiFile file) throws IOException
    {
//     * Alternativa meno performante! <<
//        doc = Jsoup.connect("https://www.docenti.unina.it/studente/visualizzaContenutoCartellaArStud.do")
//              .data("idDocente", d.getIdDocente(), "nomeDocente", d.getNomeDocente(),
//              "cognomeDocente", d.getCognomeDocente(), "codInse", ins.getCodiceInsegnamento(),
//              "percorsoPath", ins.getPercorsoPath(), "dirCorrente", "/")
//              .cookie("JSESSIONID", jsessionid)
//              .userAgent("Mozilla").timeout(3000).post();
//      ///////////////////

        // TODO Controllare se occorre riprelevare i cookies dalla connessione!
        // Così anche per le connessioni delle altre funzioni membro

        if (ApplicationUtils.getInstance().isDebuggingEnabled())
            ApplicationUtils.getInstance().log(Level.FINE, String.format("OpenResourceStream: https://www.docenti.unina.it/%s", file.getURLPath()), null);

        URL url = new URL("https://www.docenti.unina.it/" + file.getURLPath());
        HttpsURLConnection conn = (HttpsURLConnection)url.openConnection();
        conn.setRequestProperty("User-Agent", "Mozilla");
        conn.setRequestProperty("Cookie", String.format("%s=%s;", "JSESSIONID", cookies.get("JSESSIONID")));

        conn.setRequestMethod("GET");
        return conn.getInputStream();
    }

    public WebDocentiFile getResourceAssociated(Insegnamento i)
    {
        return new WebDocentiFile(i.getNomeInsegnamento(), i.getNomeInsegnamento(), i.getPercorsoPath(), true, true, i);
    }

    public List<WebDocentiFile> getResourceAssociated(WebDocentiFile folder)
            throws IOException
    {
        final List<WebDocentiFile> v = new ArrayList<WebDocentiFile>();
        final Insegnamento ins = folder.getInsegnamento();
        final Docente d = ins.getDocenteInsegnamento();

        Document doc;
        if (folder.isTopLevel())
        {
            // Se è top-level si fa un nuovo ticket (per sicurezza su multipli download di cartelle)
            // Si assume che il download è sequenziale, non multithread su stesso oggetto

            getTicket(d);

            doc = addCookies(Jsoup.connect("https://www.docenti.unina.it/studente/visualizzaContenutoCartellaArStud.do"))
              .data("idDocente", d.getIdDocente(), "nomeDocente", d.getNomeDocente(),
              "cognomeDocente", d.getCognomeDocente(), "codInse", ins.getCodiceInsegnamento(),
              "percorsoPath", ins.getPercorsoPath(), "dirCorrente", "/")
              .userAgent("Mozilla").timeout(WebDocentiUnina.connectionTimeout).post();
        }
        else
            // Se non primo livello ha direttamente link hypertesto alla risorsa (a href), con tutti i parametri GET in automatico
            doc = addCookies(Jsoup.connect("https://www.docenti.unina.it/" + folder.getURLPath()))
                    .userAgent("Mozilla").timeout(WebDocentiUnina.connectionTimeout).get();

        try
        {
            Element fileDir = doc.getElementById("fileDir");
            Elements colonne = fileDir.getElementsByTag("td");
            for (int i = 0; i < colonne.size(); i += 2)
            {
                final Element colonnaTipo = colonne.get(i);
                final Element colonnaLink = colonne.get(i+1);

                // Se hai problemi modifica con link assoluto del path! (in WebDocenti problemi di codifica caratteri!
                /*
                 * href c'e' bisogno di levare escape e riformattarlo, altrimenti non fa buona get!
                 */
    //            final String href = colonnaLink.getElementsByAttribute("href").attr("href");
//                v.add(new WebDocentiFile(colonnaLink.text(), href, parseHrefDocente(href, ParseField.percorsoPath),
//                        colonnaTipo.getElementsByAttribute("title").attr("title").equalsIgnoreCase("cartella"), false, ins));
                
                // TODO Aggiustare downloads insani in href!
                // Ho lavorato molto su tale problema, scrivo le considerazioni finali:
                // JSoup utilizza la classe Parser per modificare il documento letto e rimuovere
                // i caratteri di escape all'atto del parsing. Una modifica del comportamento
                // riesce a far ottenere l'output grezzo della pagina come la restituisce
                // il server. Tuttavia il problema non cambia, perché se si restituiscono
                // così come ricevute al server i link, ancora le cose non funzionano.
                // Dunque è preferibile lasciare il comportamento di JSoup non modificato
                // e modificare il proprio comportamento qui, con Sanitize.
                final String href = colonnaLink.getElementsByAttribute("href").attr("href");
                final String sanitizedHref = sanitizeGetURL(href);

                if (ApplicationUtils.getInstance().isDebuggingEnabled())
                    ApplicationUtils.getInstance().log(Level.FINE, String.format("Sanitize URL: %s -> %s", href, sanitizedHref), null);

                v.add(new WebDocentiFile(colonnaLink.text(), sanitizedHref, folder.getPath() + "/" + colonnaLink.text(),
                        colonnaTipo.getElementsByAttribute("title").attr("title").equalsIgnoreCase("cartella"), false, ins));

            }
        }
        catch (NullPointerException e)
        {
            // TODO Trovare buon compromesso per cartelle vuote/errori fetching
            ApplicationUtils.getInstance().log(Level.WARNING, "Errore in analisi pagina!\nQuesto errore è generato da errati dati login oppure quando non si è iscritti al corso.\nLa cartella analizzata potrebbe anche essere vuota e questo avviso è trascurabile...", e);
            if (doc != null)
                ApplicationUtils.getInstance().log(Level.INFO, "Errore accaduto in parsing pagina: " + doc.toString(), null);
//            throw new IOException("Impossibile eseguire parsing della pagina restituita!\nProva a controllare dati login! Sei iscritto al corso??", e);
        }

        return v;
    }

    public static String sanitizeGetURL(String url)
    {
        return Jsoup.parseBodyFragment(url.replaceAll(" ", "%20")).text().replaceAll("\\+", "%2B");
    }

    public void getTicket(Docente d) throws IOException
    {
        /*
         * Funzionamento: occorre fare una catena di JSESSIONID
         * si parte da una pagina, si ottiene la JSESSIONID, quindi si va in
         * materiale didattico e si seguono i link. NB: un accesso può essere a vuoto
         */

        // Request funziona anche sul welcome senza alcun parametro per ottenere soltanto JSESSIONID
//        Response r = Jsoup.connect("https://www.docenti.unina.it/studente/visualizzaAreeDownload.do")
//              .data("idDocente", "4e49434f4c414d415a5a4f4343414d5a5a4e434c36334232374638333959",
//              "nomeDocente", "NICOLA", "cognomeDocente", "MAZZOCCA", "percorso", "MATERIALE DIDATTICO")
//              .userAgent("Mozilla").execute();
//
//        r = Jsoup.connect("https://www.docenti.unina.it/login/autenticaStudente.do")
//          .data("login", this.username, "password", this.password,  "idDocente", "4e49434f4c414d415a5a4f4343414d5a5a4e434c36334232374638333959",
//          "nomeDocente", "NICOLA", "cognomeDocente", "MAZZOCCA", "percorso", "MATERIALE DIDATTICO", "sumbit", "accedi")
//          .userAgent("Mozilla").cookie("JSESSIONID", r.cookie("JSESSIONID"))
//          .timeout(3000).method(Connection.Method.POST).execute();
//
//          Connection c = Jsoup.connect("https://www.docenti.unina.it/studente/visualizzaAreeDownload.do")
//          .data("idDocente", "4e49434f4c414d415a5a4f4343414d5a5a4e434c36334232374638333959",
//          "nomeDocente", "NICOLA", "cognomeDocente", "MAZZOCCA", "percorso", "MATERIALE DIDATTICO")
//          .cookie("JSESSIONID", r.cookie("JSESSIONID"))
//          .userAgent("Mozilla").timeout(3000);

        Response r = Jsoup.connect("https://www.docenti.unina.it/Welcome.do").userAgent("Mozilla").execute();

        String jsessionid = "";
        if (r.hasCookie("JSESSIONID"))
        {
            jsessionid = r.cookie("JSESSIONID");
        }
        else
        {
            for (Element e : r.parse().getElementsByTag("form"))
            {
                String actionForm = e.attr("action");
                int id = actionForm.toLowerCase().indexOf("jsessionid=");

                if (id >= 0)
                {
                    jsessionid = actionForm.substring(id);
                    break;
                }
            }
        }
        cookies.put("JSESSIONID", jsessionid);
        if (ApplicationUtils.getInstance().isDebuggingEnabled())
            ApplicationUtils.getInstance().log(Level.FINE, String.format("getTicket: JSESSIONID = %s", jsessionid), null);


        r = addCookies(Jsoup.connect("https://www.docenti.unina.it/login/autenticaStudente.do"))
          .data("login", this.username, "password", this.password,  "idDocente", d.getIdDocente(), "nomeDocente",
          d.getNomeDocente(), "cognomeDocente", d.getCognomeDocente(), "percorso", "MATERIALE DIDATTICO", "sumbit", "accedi") // Errore in pagina sumbit
          .userAgent("Mozilla").timeout(WebDocentiUnina.connectionTimeout).method(Connection.Method.POST).execute();
       extractCookies(r);

       if (ApplicationUtils.getInstance().isDebuggingEnabled())
            ApplicationUtils.getInstance().log(Level.FINE, String.format("getTicket BODY: %s\nEND BODY\n", r.body()), null);
    }


    protected static enum ParseField { idDocente, nomeDocente, cognomeDocente, percorsoPath };


    protected static String parseHrefDocente(String s, ParseField field)
    {
        String pattern;
        switch (field)
        {
            case idDocente: pattern = "idDocente="; break;
            case nomeDocente: pattern = "nomeDocente="; break;
            case cognomeDocente: pattern = "cognomeDocente="; break;
            case percorsoPath: pattern = "percorsoPath="; break;
            default: return null;
        }
        int from = s.indexOf(pattern);
        if (from < 0) return null;
        int to = s.indexOf("&", from);

        if (to == -1)
            return s.substring(from+pattern.length());
        return s.substring(from+pattern.length(), to);
    }

    public void setLogin(String username, String password) {
        this.username = username;
        this.password = password;
        loginSetted = true;
    }

    public boolean isLoginSetted()
    {
        return loginSetted;
    }

    transient private String username;
    transient private String password;
    private Map<String, String> cookies;
    transient private boolean loginSetted;
    private final static int connectionTimeout = 10000;

    public static WebDocentiUnina getInstance()
    {
        if (instance == null)
            instance = new WebDocentiUnina();
        return instance;
    }

    private static WebDocentiUnina instance = null;
}
